
Imports std

Namespace urlUtils
    Public Module urlUtils

#If Not (PocketPC Or Smartphone) Then
        Friend WithEvents config As configMonitor
#Else
        Friend config As configParser
#End If
        Public ReadOnly httpAddressHeaderLen As Int32 = strlen(network.httpAddressHeader)
        Public ReadOnly httpsAddressHeaderLen As Int32 = strlen(network.httpsAddressHeader)
        Public Const allseperator As String = network.protocolMark + network.domainSeperator + network.pathSeperator _
                                            + network.userMark + network.questionMark + network.argumentSeperator
        Public Const parentPathMark As String = network.parentPathMark
        Public Const thislevelPathMark As String = network.thislevelPathMark

        Friend Function initial() As Boolean
            assert(Not config Is Nothing, "config is nothing before initial.")
            'do not need to initialize now
            Return True
        End Function

#If Not (PocketPC Or Smartphone) Then
        Public Function initial(ByRef inputConfig As configMonitor) As Boolean
#Else
        Public Function initial(ByRef inputConfig As configParser) As Boolean
#End If
            config = inputConfig
            Return initial()
        End Function

        Public Function isUrlSeperator(ByVal c As Char) As Boolean
            Return allseperator.IndexOf(c) <> npos
        End Function

        Public Function parseArgument(ByVal arg As String _
                                      , Optional ByVal lowercase As Boolean = False _
                                      , Optional ByVal e As Text.Encoding = Nothing) As pair(Of String, String)
            'the encoding and decoding should be handled in urlencode and urldecode
#If RETIRED Then
            'the default encoding must be the same as the one in combineQuery
            If e Is Nothing Then
                e = encoding.utf8()
            End If
#End If
            If isemptystring(arg) Then
                Return Nothing
            End If

            Dim index As Int32
            Dim name As String = Nothing
            Dim value As String = Nothing
            index = arg.IndexOf(network.argumentNameValueSeparator)
            If index = npos Then
                name = emptyString
                value = arg
            Else
                name = strleft(arg, index)
                value = strmid(arg, index + 1)
            End If
            name = urldecode(name, e)
            value = urldecode(value, e)

            If lowercase Then
                name = name.ToLower()
                value = value.ToLower()
            End If

            Return make_pair(name, value)
        End Function

        Public Function parseArguments(ByVal args As vector(Of String) _
                                       , ByRef rst As vector(Of pair(Of String, String)) _
                                       , Optional ByVal lowercase As Boolean = False _
                                       , Optional ByVal e As Text.Encoding = Nothing) As Boolean
            If rst Is Nothing Then
                rst = New vector(Of pair(Of String, String))()
            Else
                rst.clear()
            End If

            If args Is Nothing OrElse args.size() = 0 Then
                Return True
            End If

            Dim i As Int64
            For i = 0 To args.size() - 1
                Dim p As pair(Of String, String) = Nothing
                p = parseArgument(args(i), lowercase, e)
                If Not p Is Nothing Then
                    rst.push_back(p)
                End If
            Next

            Return True
        End Function

        Public Function parseArguments(ByVal query As String _
                                       , ByRef results As vector(Of pair(Of String, String)) _
                                       , Optional ByVal lowercase As Boolean = False _
                                       , Optional ByVal e As Text.Encoding = Nothing) As Boolean
            If isemptystring(query) Then
                Return True
            End If

            If query(0) = network.questionMark Then
                query = strmid(query, 1)
            End If

            Dim args As vector(Of String) = Nothing
            strsepar(query, network.argumentSeperator, args, True)
            Return parseArguments(args, results, lowercase, e)
        End Function

        Public Function parseQueryString(ByVal querystring As String _
                                         , ByRef results As vector(Of pair(Of String, String)) _
                                         , Optional ByVal lowercase As Boolean = False _
                                         , Optional ByVal e As Text.Encoding = Nothing) As Boolean
            Return parseArguments(querystring, results, lowercase, e)
        End Function

        Public Function combineQuery(ByVal arguments As vector(Of pair(Of String, String)) _
                                     , Optional ByVal encoding As Text.Encoding = Nothing) As String
            Return combineUrl(Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, arguments, Nothing, encoding)
        End Function

        Public Function combineQuery(ByVal arguments As vector(Of String)) As String
            Return combineUrl(Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, arguments, Nothing)
        End Function

        Public Function combineQuery(ByVal encoding As Text.Encoding, ByVal ParamArray args() As String) As String
            Dim arguments As vector(Of pair(Of String, String)) = Nothing
            arguments = New vector(Of pair(Of String, String))()
            Dim i As Int64
            For i = 0 To args.Length() - 2 Step 2
                arguments.push_back(make_pair(args(i), args(i + 1)))
            Next

            Return combineQuery(arguments, encoding)
        End Function

        Public Function combineQuery(ByVal ParamArray args() As String) As String
            Return combineQuery(Nothing, args)
        End Function

        Public Function appendQuery(ByVal baseaddress As String, ByVal argument As String) As String
            If isemptystring(argument) Then
                Return baseaddress
            Else
                If strsame(argument, 0, network.questionMark, 0, 1, False) Then
                    'if there is already a question mark in argument, remove it
                    argument = strmid(argument, 1)
                End If
                If strcont(baseaddress, network.questionMark, False) Then
                    'already have a question mark <?> in baseaddress
                    If strsame(baseaddress, strlen(baseaddress) - 1, network.argumentSeperator, 0, 1, False) Then
                        'last character of baseaddress is argument seperator <&>, directly append argument to baseaddress
                        Return baseaddress + argument
                    Else
                        'otherwise add an argument seperator <&>, then argument
                        Return baseaddress + network.argumentSeperator + argument
                    End If
                Else
                    'no question mark <?> in baseaddress, append a question mark <?>, then argument
                    Return baseaddress + network.questionMark + argument
                End If
            End If
        End Function

        Public Function appendQuery(ByVal baseaddress As String, ByVal arguments As vector(Of pair(Of String, String)) _
                                    , Optional ByVal encoding As Text.Encoding = Nothing) As String
            Return appendQuery(baseaddress, combineQuery(arguments, encoding))
        End Function

        Public Function appendQuery(ByVal baseaddress As String, ByVal arguments As vector(Of String)) As String
            Return appendQuery(baseaddress, combineQuery(arguments))
        End Function

        Public Function appendQuery(ByVal baseaddress As String, ByVal encoding As Text.Encoding _
                                    , ByVal ParamArray args() As String) As String
            Return appendQuery(baseaddress, combineQuery(encoding, args))
        End Function

        Public Function appendQuery(ByVal baseaddress As String, ByVal ParamArray args() As String) As String
            Return appendQuery(baseaddress, combineQuery(args))
        End Function

        Public Function isthislevelPath(ByVal s As String) As Boolean
            Return strsame(s, thislevelPathMark, False) _
                OrElse strsame(s, pathseperator, False) _
                OrElse strsame(s, thislevelPathMark + pathseperator, False)
        End Function

        Public Function isparentPath(ByVal s As String) As Boolean
            Return strsame(s, parentPathMark, strlen(parentPathMark), False) _
                OrElse strsame(s, parentPathMark + pathseperator, False)
        End Function

        Public Function combineUrl(ByVal protocol As String, ByVal user As String _
                                   , ByVal password As String, ByVal server As String _
                                   , ByVal port As String, ByVal path As String _
                                   , ByVal argument As String, ByVal position As String) As String
            Dim rtn As String = Nothing
            If Not isemptystring(protocol) Then
                If protocol.IndexOf(network.protocolMark) <> npos Then
                    rtn += protocol
                Else
                    rtn += protocol + network.protocolMark + network.pathSeperator + network.pathSeperator
                End If
            End If
            If Not isemptystring(user) Then
                rtn += user
                If Not isemptystring(password) Then
                    rtn += network.userPasswordSeperator + password
                End If
                rtn += network.userMark
            End If
            If Not isemptystring(server) Then
                rtn += server
                If Not isemptystring(port) Then
                    rtn += network.portMark + port
                End If
                rtn += network.pathSeperator
            End If
            If Not isemptystring(path) Then
                rtn += path
            End If
            If Not isemptystring(argument) Then
                rtn += network.questionMark + argument
            End If
            If Not isemptystring(position) Then
                rtn += network.linkPositionMark + position
            End If
            Return rtn
        End Function

        Public Function combineUrl(ByVal protocol As String, ByVal user As String _
                                   , ByVal password As String, ByVal server As String _
                                   , ByVal port As String, ByVal paths As vector(Of String) _
                                   , ByVal arguments As vector(Of String) _
                                   , ByVal position As String) As String
            Dim path As String = Nothing
            If Not paths Is Nothing AndAlso paths.size() > 0 Then
                Dim i As Int64
                i = 0
                While i < paths.size()
                    If isemptystring(paths(i)) OrElse isthislevelPath(paths(i)) Then
                        paths.erase(i)
                    ElseIf isparentPath(paths(i)) Then
                        paths.erase(i - 1, i + 1)
                        i = ifoper(i = 0, 0, i - 1)
                    Else
                        i += 1
                    End If
                End While
#If RETIRED Then
                    If Not strsame(paths(i), thislevelPathMark, strlen(thislevelPathMark)) Then
                        If i < paths.size() - 1 AndAlso strsame(paths(i + 1), parentPathMark, strlen(parentPathMark)) Then
                            i += 1
                        Else
                            If Not isemptystring(paths(i)) Then
                                If i < paths.size() - 1 _
                                    AndAlso paths(i)(strlen(paths(i)) - 1) <> network.pathSeperator Then
                                    rtn += paths(i) + network.pathSeperator
                                Else
                                    rtn += paths(i)
                                End If
                            End If
                        End If
                    End If
#End If
                For i = 0 To paths.size() - 2
                    path += paths(i)
                    If paths(i)(strlen(paths(i)) - 1) <> pathseperator Then
                        path += pathseperator
                    End If
                Next
                If i < paths.size() Then
                    path += paths(i)
                End If
            End If
            Return combineUrl(protocol, user, password, server, port, path _
                              , ifoper(arguments Is Nothing OrElse arguments.size() = 0 _
                                       , Nothing, arguments.ToString(network.argumentSeperator)) _
                              , position)
        End Function

        Public Function combineUrl(ByVal protocol As String, ByVal user As String _
                                   , ByVal password As String, ByVal server As String _
                                   , ByVal port As String, ByVal paths As vector(Of String) _
                                   , ByVal arguments As vector(Of pair(Of String, String)) _
                                   , ByVal position As String _
                                   , Optional ByVal e As Text.Encoding = Nothing) As String
            'the encoding and decoding should be handled in urlencode and urldecode
#If RETIRED Then
            'the default encoding must be the same as the one in parseArgument
            If e Is Nothing Then
                e = encoding.utf8
            End If
#End If
            Dim args As vector(Of String) = Nothing
            If Not arguments Is Nothing Then
                args = New vector(Of String)()
                Dim i As Int64
                For i = 0 To arguments.size() - 1
                    Dim emptyfirst As Boolean
                    Dim arg As String = Nothing
                    emptyfirst = isemptystring(arguments(i).first)
                    If emptyfirst AndAlso isemptystring(arguments(i).second) Then
                        arg = Nothing
                    ElseIf emptyfirst Then
                        arg = urlencode(arguments(i).second, e)
                    Else
                        arg = urlencode(arguments(i).first, e) + _
                              network.argumentNameValueSeparator + _
                              urlencode(arguments(i).second, e)
                    End If
                    If Not isemptystring(arg) Then
                        args.push_back(arg)
                    End If
                Next
            End If

            Return combineUrl(protocol, user, password, server, port, paths, args, position)
        End Function

        Public Function splitUrl(ByVal url As String, ByRef protocol As String, ByRef user As String _
                                                    , ByRef password As String, ByRef server As String _
                                                    , ByRef port As String, ByRef path As String _
                                                    , ByRef query As String, ByRef position As String _
                                                    , ByRef unknown As String) As Boolean
            Dim index As Int32
            kickLeftRightBlank(url)

            protocol = emptyString
            user = emptyString
            password = emptyString
            server = emptyString
            port = emptyString
            path = emptyString
            query = emptyString
            position = emptyString
            unknown = emptyString

            If Not isemptystring(url) Then
                index = url.IndexOf(network.protocolMark)
                If index <> npos Then
                    protocol = strleft(url, index)
                    If strcont(protocol, AddressOf isUrlSeperator) Then
                        protocol = Nothing
                    Else
                        index += 1
                        While index < strlen(url) AndAlso url(index) = network.pathSeperator
                            index += 1
                        End While
                        url = strmid(url, index)
                    End If
                End If

                If Not isemptystring(url) Then
                    index = url.IndexOf(network.userMark)
                    If index <> npos Then
                        user = strleft(url, index)
                        url = strmid(url, index + 1)
                        index = user.IndexOf(network.userPasswordSeperator)
                        If index <> npos Then
                            password = strmid(user, index + 1)
                            user = strleft(user, index)
                        End If
                    End If
                End If

                If Not isemptystring(url) Then
                    index = url.IndexOf(network.pathSeperator)
                    If index = npos Then
                        index = url.IndexOf(network.questionMark)
                    End If
                    If index = npos Then
                        index = url.IndexOf(network.linkPositionMark)
                    End If
                    If index = npos Then
                        server = url
                        url = Nothing
                    Else
                        server = strleft(url, index)
                        url = strmid(url, index)
                    End If
                End If

                If Not isemptystring(server) Then
                    index = server.IndexOf(network.portMark)
                    If index <> npos Then
                        port = strmid(server, index + 1)
                        server = strleft(server, index)
                    End If
                End If

                If Not isemptystring(url) AndAlso url(0) = network.pathSeperator Then
                    index = url.IndexOf(network.questionMark)
                    If index = npos Then
                        index = url.IndexOf(network.linkPositionMark)
                    End If
                    If index = npos Then
                        path = strmid(url, 1)
                        url = Nothing
                    Else
                        path = strmid(url, 1, index - 1)
                        url = strmid(url, index)
                    End If
                End If

                If Not isemptystring(url) AndAlso url(0) = network.questionMark Then
                    index = url.IndexOf(network.linkPositionMark)
                    If index = npos Then
                        query = strmid(url, 1)
                        url = Nothing
                    Else
                        query = strmid(url, 1, index - 1)
                        url = strmid(url, index)
                    End If
                End If

                If Not isemptystring(url) AndAlso url(0) = network.linkPositionMark Then
                    copy(position, strmid(url, 1))
                    url = Nothing
                End If

                If Not isemptystring(url) Then
                    copy(unknown, url)
                End If

                Return True
            Else
                Return False
            End If
        End Function

        Public Function splitUrl(ByVal url As String, ByRef protocol As String, ByRef user As String _
                                                    , ByRef password As String, ByRef server As String _
                                                    , ByRef port As String, ByRef paths As vector(Of String) _
                                                    , ByRef arguments As vector(Of String) _
                                                    , ByRef position As String _
                                                    , ByRef unknown As String) As Boolean
            Dim path As String = Nothing
            Dim query As String = Nothing
            If splitUrl(url, protocol, user, password, server, port, path, query, position, unknown) Then
                strsepar(path, network.pathSeperator, paths, True)
                If paths Is Nothing Then
                    paths = New vector(Of String)()
                ElseIf Not isemptystring(path) Then
                    Dim i As Int64
                    For i = 0 To paths.size() - 2
                        paths(i) += network.pathSeperator
                    Next
                    If path(strlen(path) - 1) = network.pathSeperator Then
                        paths(i) += network.pathSeperator
                    End If
                End If
                strsepar(query, network.argumentSeperator, arguments, True)
                If arguments Is Nothing Then
                    arguments = New vector(Of String)()
                End If
                Return True
            Else
                Return False
            End If
        End Function

        Public Function splitDomains(ByVal url As String, ByRef results As vector(Of String)) As Boolean
            Dim server As String = Nothing
            If splitUrl(url, Nothing, Nothing, Nothing, server, Nothing, "", "", Nothing, Nothing) Then
                strsepar(server, network.domainSeperator, results)
                Return True
            Else
                raiseError("cannot splitUrl, url " + url, errorHandle.errorType.warning)
                Return False
            End If
        End Function

        Public Function parentpath(ByVal url As String) As String
            Static pathSeperatorLen As Int32 = strlen(network.pathSeperator)
            Dim index As Int32
            If strsame(strright(url, pathSeperatorLen), network.pathSeperator, pathSeperatorLen) Then
                url = strleft(url, strlen(url) - pathSeperatorLen)
            End If
            index = url.LastIndexOf(network.pathSeperator)
            If index <> npos AndAlso (index >= httpAddressHeaderLen OrElse index >= httpsAddressHeaderLen) Then
                Return strleft(url, index + 1)
            Else
                Return url + network.pathSeperator
            End If
        End Function

        Public Function rootpath(ByVal url As String) As String
            Dim root As String = Nothing
            copy(root, url)
            Do
                url = root
                root = parentpath(url)
            Loop Until strsame(root, url)
            Return root
        End Function

        Public Function matchDefaultPort(ByVal protocol As String, ByVal port As String) As Boolean
            Return (strsame(port, Convert.ToString(network.httpPort), False) _
                    AndAlso strsame(protocol, network.httpProtocol, False))
        End Function

        Public Function urlnormalize(ByRef url As String) As Boolean
            Dim protocol As String = Nothing
            Dim user As String = Nothing
            Dim password As String = Nothing
            Dim server As String = Nothing
            Dim port As String = Nothing
            Dim paths As vector(Of String) = Nothing
            Dim arguments As vector(Of String) = Nothing
            Dim position As String = Nothing
            Dim unknown As String = Nothing

            If splitUrl(url, protocol, user, password, server, port, paths, arguments, position, unknown) Then
                If isemptystring(protocol) Then
                    protocol = network.httpProtocol
                End If
                If matchDefaultPort(protocol, port) Then
                    port = Nothing
                End If

                url = combineUrl(protocol, user, password, server, port, paths, arguments, position)
                Return True
            Else
                raiseError("cannot splitUrl, url " + url, errorHandle.errorType.warning)
                Return False
            End If
        End Function

        Public Function normalize(ByRef url As String) As Boolean
            Return urlnormalize(url)
        End Function

        Sub New()
            config = Nothing
        End Sub

#If Not (PocketPC Or Smartphone) Then
        Private Sub config_ConfigChanged() Handles config.ConfigChanged
            initial()
        End Sub
#End If

        Public Class unittests
            Public Shared Sub splitUrl()
                Dim url As String = Nothing
                Dim protocol As String = Nothing
                Dim user As String = Nothing
                Dim password As String = Nothing
                Dim server As String = Nothing
                Dim port As String = Nothing
                Dim paths As vector(Of String) = Nothing
                Dim arguments As vector(Of String) = Nothing
                Dim position As String = Nothing
                Dim unknown As String = Nothing

                Console.WriteLine("******* splitUrlUnitTest *******")
                url = Console.ReadLine()
                While Not url Is Nothing
                    Console.WriteLine("********************************")
                    If urlUtils.splitUrl(url, protocol, user, password, server, port, paths, _
                                         arguments, position, unknown) Then
                        Console.WriteLine("url " + url)
                        Console.WriteLine("protocol " + protocol)
                        Console.WriteLine("user " + user)
                        Console.WriteLine("password " + password)
                        Console.WriteLine("server " + server)
                        Console.WriteLine("port " + port)
                        Console.WriteLine("paths " _
                                        + paths.ToString(character.blank + network.pathSeperator + character.blank))
                        Console.WriteLine("arguments " _
                                        + arguments.ToString(character.blank + network.argumentSeperator + character.blank))
                        Console.WriteLine("position " + position)
                        Console.WriteLine("unknown " + unknown)
                    Else
                        raiseError("cannot splitUrl, url " + url, errorHandle.errorType.warning)
                    End If
                    Console.WriteLine("********************************")

                    url = Console.ReadLine()
                End While
            End Sub

            Public Shared Sub splitDomains()
                Dim url As String = Nothing
                Dim domains As vector(Of String) = Nothing

                Console.WriteLine("**** splitDomainsUnitTest *****")
                url = Console.ReadLine()
                While Not url Is Nothing
                    Console.WriteLine("********************************")
                    If urlUtils.splitDomains(url, domains) Then
                        Console.WriteLine("domains " + domains.ToString(character.blank))
                    Else
                        raiseError("cannot splitDomains, url " + url, errorHandle.errorType.warning)
                    End If
                    Console.WriteLine("********************************")

                    url = Console.ReadLine()
                End While
            End Sub

            Public Shared Sub urlnormalize()
                Dim url As String = Nothing

                Console.WriteLine("***** urlnormalizeUnitTest *****")
                url = Console.ReadLine()
                While Not url Is Nothing
                    Console.WriteLine("********************************")
                    If urlUtils.urlnormalize(url) Then
                        Console.WriteLine("normalized url " + url)
                    Else
                        raiseError("cannot splitDomains, url " + url, errorHandle.errorType.warning)
                    End If
                    Console.WriteLine("********************************")

                    url = Console.ReadLine()
                End While
            End Sub
        End Class
    End Module
End Namespace
